#include "./includes/utility.h"

//Print errors and quit
void error(const char* msg){
	perror(msg);
	exit(1);
}

//deletes white spaces from a string
char* str_trim(char* string){
	int dim,i,j;
	char* str;
	dim = strlen(string);
	str = malloc(dim+1);
	memset(str,0,dim+1);
	i=0;
	j=0;
	while(i<dim){
		if (string[i] != ' '){
			str[j] = string[i];
			j++;
		}
		i++;
	}
	str[strlen(str)]  = '\0';
	return realloc(str,strlen(str)+1);
}

//generates Nonce
void create_nonce(unsigned char *buf){
	if (RAND_bytes(buf, NONCELEN) != 1)
		error("Nonce generation error!");
	return;
}
//Print a byte in HEX format
void printbyte(char b){
  char c;
  c=b;
  c=c>>4;
  c=c&15;
  printf("%X",c);
  c=b;
  c=c&15;
  printf("%X",c);
}
//Print a string in HEX format
void printH(char* k,int len){
  int i;
  for(i=0;i<len-1;i++)
    printbyte(k[i]);
  printbyte(k[len-1]);
  printf("\n");
}
//Writes characters contained into *in into file
void write_file(char *in,char *file,int len){
	FILE *fp;
	int ret;
	int i;
	fp = fopen(file,"wb+");
	if(fp == NULL){
		printf("Error: Can't open the file %s\n",file);
	}
	for(i=0;i<len;i++){
		if(fputc(in[i],fp) == -1){
			printf("Error: Can't write the file %s\n",file);
		}
	}
	fclose(fp);
}
//Reads characters from file and puts them into *out
void read_file(char *out,char *file,int len){
	FILE *fp;
	int i;
	fp = fopen(file,"rb");
	if(fp == NULL){
		printf("Error: Can't open the file %s\n",file);
	}
	for(i=0;i<len;i++){
		out[i] = fgetc(fp);
	}
	fclose(fp);
}
//Returns file length
int file_len(char *file){
	FILE *fp;
	int len;
	fp = fopen(file,"rb");
	if(fp == NULL){
		printf("Error: Can't open the file %s\n",file);
	}
	fseek(fp, 0L, SEEK_END);
	len = ftell(fp);
	fclose(fp);
	return len;
}
/*******************************************************************************
 * 			CRYPTO FUNCTIONS
*******************************************************************************/
//generate random key
void select_random_key(char* key,int key_size) {
  RAND_bytes(key,key_size);
}
//context allocation & preparation
void encrypt_ctx_init(EVP_CIPHER_CTX* ctx, char* key, int key_size) {
  EVP_CIPHER_CTX_init(ctx);
  EVP_EncryptInit(ctx,EVP_des_ecb(),NULL,NULL);
  EVP_CIPHER_CTX_set_key_length(ctx,key_size);
  EVP_EncryptInit(ctx,NULL,key,NULL);
}
//context allocation & preparation
void decrypt_ctx_init(EVP_CIPHER_CTX* ctx, char* key, int key_size) {
  EVP_CIPHER_CTX_init(ctx);
  EVP_DecryptInit(ctx,EVP_des_ecb(),NULL,NULL);
  EVP_CIPHER_CTX_set_key_length(ctx,key_size);
  EVP_DecryptInit(ctx,NULL,key,NULL);
}
//encrypts a plaintext and returns encrypted bytes
int encrypt(unsigned char* key, int keylen, unsigned char* ciphertext, int pt_len, unsigned char* plaintext) {
	EVP_CIPHER_CTX* ctx;
	int nc_tot,cn_ptr,nc;
	nc_tot = 0;
	cn_ptr = 0;
	nc = 0;
	ctx = (EVP_CIPHER_CTX*)malloc(sizeof(EVP_CIPHER_CTX));
    encrypt_ctx_init(ctx,key,keylen);
	EVP_EncryptUpdate(ctx,ciphertext,&nc,plaintext,pt_len);
	nc_tot += nc;
	cn_ptr += nc;
	EVP_EncryptFinal(ctx,&ciphertext[cn_ptr],&nc);
	nc_tot += nc;
	EVP_CIPHER_CTX_free(ctx);
	return nc_tot;
}
//decrypts a ciphertext and returns decrypted bytes
int decrypt(unsigned char* key, int keylen, unsigned char* ciphertext, int ct_len, unsigned char* plaintext) {
	EVP_CIPHER_CTX* ctx;
	int nc_tot,cn_ptr,nc;
	nc_tot = 0;
	cn_ptr = 0;
	nc = 0;
	ctx = (EVP_CIPHER_CTX*)malloc(sizeof(EVP_CIPHER_CTX));
	decrypt_ctx_init(ctx,key,keylen);
	EVP_DecryptUpdate(ctx,plaintext,&nc,ciphertext,ct_len);
	nc_tot += nc;
	cn_ptr += nc;
	EVP_DecryptFinal(ctx,&plaintext[cn_ptr],&nc);
	nc_tot += nc;
	EVP_CIPHER_CTX_free(ctx);
	return nc_tot;
}
//Sends an encrypted message through socket sk
void TCP_send_enc_msg(unsigned char* key, int keylen,int sk,char* msg, int msglen){
	unsigned char* datatosend,*tmp;
	int datasize,ret;
	//setup message
	datasize = msglen + block_size();
	tmp = (unsigned char*)malloc(datasize);
	//SYMMETRIC ENCRYPT
	datasize = encrypt(key,keylen,tmp,msglen,(unsigned char*)msg);
	datatosend = (unsigned char*)malloc(datasize);
	memcpy(datatosend,tmp,datasize);
	//send data length
	ret = TCP_send(sk,(void*)&datasize,sizeof(datasize));
	//send data
	ret = TCP_send(sk,(void*)datatosend,datasize);
	free(tmp);
	free(datatosend);
}
//Receives an encrypted message from socket sk and decrypts it
char* TCP_recv_enc_msg(unsigned char* key, int keylen,int sk,int* msgsize){
	int datasize;
	char* msg;
	unsigned char* datarecv;
	unsigned char* tmp;
	int tmpsize,ret;
	ret = TCP_recv(sk, (void *) &datasize, sizeof(int));
	if(ret == 0){
		return NULL;
	}
	datarecv = (unsigned char*) malloc(datasize);
	ret = TCP_recv(sk, (void *) datarecv, datasize);
	if(ret == 0){
		free(datarecv);
		return NULL;
	}
	//setup message
	tmpsize = datasize + block_size();
	tmp = (unsigned char*)malloc(tmpsize);
	//SYMMETRIC ENCRYPT
	*msgsize = decrypt(key,keylen,datarecv,datasize,tmp);
	msg = (char*)malloc(*msgsize+1);
	memset(msg,0,*msgsize+1);
	memcpy(msg,tmp,*msgsize);
	msg[*msgsize] = '\0';
	free(datarecv);
	free(tmp);
	return msg;
}

/*******************************************************************************
 * 			COMMUNICATION FUNCTIONS
*******************************************************************************/

//TCP_SOCKET_INIT
void TCP_init(int* sk, struct sockaddr_in* sst,const char* addr,int port){
  *sk = socket(PF_INET,SOCK_STREAM,0);
  if(*sk == -1){
    error("Error in socket creation!");
  }
  memset(sst,0,sizeof(*sst));
  sst -> sin_family = AF_INET;
  sst -> sin_port = htons(port);
  if(inet_pton(AF_INET,addr,&(sst -> sin_addr)) == 0){
    error("Error in address translation");
  }
}
//TCP_CONNECT
void TCP_connect(int sk,struct sockaddr_in* sst){
  if(connect(sk,(struct sockaddr*)sst,sizeof(*sst)) == -1){
    error("Error during connection!");
  }
}
//TCP_SERVER
void TCP_bind(int sk, struct sockaddr_in* sst){
  if(bind(sk,(struct sockaddr*) sst,sizeof(*sst)) == -1){
	  error("Error in bind()");
  }
  if(listen(sk,10) == -1){
	  error("Error in listen()");
  }
}
//TCP_LISTEN
void TCP_listen(int sk,int n){
  if(listen(sk,n) == -1){
    error("Error in listen()");
  }
}
//TCP_ACCEPT
void TCP_accept(int sk,int* cl,struct sockaddr_in* clnt,socklen_t* len){
  if((*cl = accept(sk,(struct sockaddr*)clnt,len)) == -1){
    error("Error in accept()");
  }
}

//TCP_SEND
int TCP_send(int sk,void* msg,int len){
  int ret;
  ret = send(sk,msg,len,0);
  if(ret == -1 || ret<len){
	  error("Error in send()");
  }
  return ret;
}
//TCP_RECV
int TCP_recv(int sk,void* msg,int len){
  int ret;
  ret = recv(sk,msg,len,MSG_WAITALL);
  if(ret < 0){
	  error("Error in recv()");
  }
  return ret;
}

//TCP_CLOSE
void TCP_close(int sk){
  if(close(sk) == -1){
    error("Error in close()\n");
  }
}

//Reads from stdin and deletes \n if present
void sgets(char * buf, int len) {
	memset(buf,0,len);
	char c;
	fgets(buf, len, stdin); //fgets legge al più len-1 caratteri e inserisce \0 alla fine
	if (buf[strlen(buf)-1]=='\n')
	  buf[strlen(buf)-1]='\0';
	else
	  while ((c=getchar())!=EOF && c!='\n'); //Per ripulire lo stdin
}

//Inserts a new client in the head of list of connected clients
void ins_list(struct client** list, struct client* elem) {
	elem->next=*list;
	*list=elem;
}

//Deletes a client from the list of connected clients
int rmID_list(struct client** list,int id_sck,char* usr) {
	struct client* p=NULL;
	struct client* q;
	for (q=*list; q!=NULL && q->id_sck!=id_sck; q=q->next)
		p=q;
	if (q==NULL) return -1;
	strcpy(usr,q->username); //copy client name
	if (q==*list)
		*list=q->next;
	else
		p->next=q->next;
	free((void *)q);
	return 0;
}
//Looks for a connected client by socket
struct client* checkID_list(struct client* list,int id_sck) {
	struct client* q;
	for (q=list; q!=NULL && ((q->id_sck)!=id_sck); q=q->next);
	return q;
}
//Looks for a connected client by username
int checkUsername_list(struct client* list,char* usr) {
	struct client* q;
	q=list;
	while(q!=NULL){
		if(strcmp(q->username, usr)==0){
			return 1;
		}
		q = q->next;
	}
	return 0;
}
//Looks for a registered client by username and password
struct reg_client* checkUsr_list(struct reg_client* list,char* usr,char* pssw) {
	struct reg_client* q;
	//for (q=list; q!=NULL && strcmp(q->username, usr)!=0 && strcmp(q->password, pssw)!=0; q=q->next);
	q=list;
	while(q!=NULL){
		if(strcmp(q->username, usr)==0){
			if(strcmp(q->password, pssw)==0)
				return q;
		}
		q = q->next;
	}
	return NULL;
}
//Removes a client from connected clients list
int remove_client(struct client** list, char* usr) {
	struct client* p=NULL;
	struct client* q;
	for (q=*list; q!=NULL && strcmp(q->username,usr)!=0; q=q->next)
		p=q;
	if (q==NULL) return -1; //client not present
	if (q==*list)
		*list=q->next;
	else
		p->next=q->next;
	free(q);
	return 0;
}
//---------------------------------------------------------------------------------
// FUNCTION TO MANAGE USER'S COMMAND
//---------------------------------------------------------------------------------

//print directory's content
char* ls(char* name){
	 DIR *dir;
	 struct dirent* dd;
	 char *buff,*ret;
	 dir = opendir (name);
	 buff = (char*)malloc(1);
	 memset(buff,0,1);
	 int size,addr;
	 size = 0;
	 addr = 0;
	 while ((dd = readdir(dir)) != NULL){
		if(strcmp(dd->d_name,".") != 0 && strcmp(dd->d_name,"..") != 0){
			size += strlen(dd->d_name)+1;
			ret = realloc(buff,size);
			buff = ret;
			strcpy(&buff[addr],dd->d_name);
			buff[size-1] = '\n';
			addr += strlen(dd->d_name)+1;
			ret = NULL;
		}
	 }
	 closedir(dir);
	 ret = (char*)malloc(size+1);
	 memcpy(ret,buff,size);
	 ret[size] = '\0';
	 free(buff);
	 return ret;
}
//Returns block size for the cipher
int block_size(){
	return EVP_CIPHER_block_size(EVP_des_ecb());
}

//sends an encrypted file
void send_enc_file(char* sender_file, unsigned char* key, int keylen, int sk){
	int length;
	char* receiver_buff;
	length = file_len(sender_file);
	receiver_buff = (char*)malloc(length);
	read_file(receiver_buff, sender_file, length);
	TCP_send_enc_msg(key, keylen, sk, receiver_buff, length);
	free(receiver_buff);
	return;
}
//receives an encrypted file and decrypts it
void receive_enc_file(char* receiver_file, unsigned char* key, int keylen, int sk){
	int length;
	char* sender_buff;
	sender_buff = TCP_recv_enc_msg(key, keylen, sk,&length);
	write_file(sender_buff, receiver_file, length);
	free(sender_buff);
	return;
}
//looks for a file in a directory
bool find_file(char dir_name[], char file_name[]){
	 DIR *dir;
	 struct dirent* dd;
	 dir = opendir (dir_name);
	 while ((dd = readdir(dir)) != NULL){ 
		if (strcmp(file_name, dd->d_name) == 0){
			closedir(dir);
			return true;
		}
	 }
	 closedir(dir);
	 return false;
}
//generates the registered client list
struct reg_client* generate_reg_client_list(char* filename){
	FILE *fp;
	char* temp;
	int nbytes = 256;
	int res;
	struct reg_client* list = NULL;
	struct reg_client* client = NULL;
	fp = fopen(filename,"r");
	if(fp == NULL){
		printf("Error: Can't open the file %s\n",filename);
		return NULL;
		//exit(1);
	}
	temp = (char*)malloc(nbytes);
	while((res = getline(&temp, (size_t*)&nbytes, fp)) != -1){
		if (list == NULL){
			list = (struct reg_client*) malloc(sizeof(struct reg_client));
			client = list;
		}
		else{
			client->next = (struct reg_client*) malloc(sizeof(struct reg_client));
			client = client->next;
		}
		memset(client,0,sizeof(struct reg_client));
		strcpy(client->username, strtok(temp, " "));
		strcpy(client->password, strtok(NULL, " "));
		client->password[strlen(client->password)-1] = '\0';
	}
	client->next = NULL;
	fclose(fp);
	return list;
}

